//
//  AAWGame.mm
//  AAWGameCore
//
//  Created by David Wagner on 29/06/2009.
//  Copyright 2009 Zygote Commando. All rights reserved.
//

#import "AAWGame.h"
#import "AAWActor.h"
#import "AAWGameCoreView.h"
#import "AAWGameSettings.h"

#pragma mark Private declarations
@interface AAWGame ()

@property (nonatomic, retain) NSMutableArray* liveActors;
@property (nonatomic, retain) NSMutableArray* deadActors;

- (AAWActor*)addBoxAt:(GLfloat)x y:(GLfloat)y width:(GLfloat)width height:(GLfloat)height density:(GLfloat)density friction:(GLfloat)friction;

- (void)initPhysicsWorld;
- (void)initGameWorld;
- (void)teardownPhysicsWorld;
- (void)teardownGameWorld;
- (void)clearOutTheDead;

@end


@implementation AAWGame

#pragma mark Properties
@synthesize world;
@synthesize liveActors;
@synthesize deadActors;
@synthesize canHireActors;

- (BOOL)canHireActors
{
	if( liveActors != nil && [liveActors count] < b2_maxProxies )
	{
		return YES;
	}
	
	return NO;
}

- (float)timerInterval
{
	return 1.0f/120.0f;
}

- (float)updateInterval
{
	return 1.0f/60.0f;
}

- (float)renderInterval
{
	return 1.0f/30.0f;
}

- (float)maxCatchupInterval
{
	return self.updateInterval * 4;
}

#pragma mark Class Methods

#pragma mark Instance Methods
- (id)initWithView:(AAWGameCoreView*) gameCoreView
{
	if(self = [super init])
	{
		view = [gameCoreView retain];
		
		[self initPhysicsWorld];
		[self initGameWorld];
		
		lastUpdate = [NSDate timeIntervalSinceReferenceDate];

        [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0f/5.0f)];
        [[UIAccelerometer sharedAccelerometer] setDelegate:self];	
	}
	
	return self;
}

- (void)dealloc 
{    
	[self teardownGameWorld];
	[self teardownPhysicsWorld];
    [view release];
    
    [super dealloc];
}

- (void)initPhysicsWorld
{
	b2AABB worldAABB;
	worldAABB.lowerBound.Set(-512.0f, -512.0f);
	worldAABB.upperBound.Set(512.0f, 512.0f);
	
	gravity.x =  0.0f;
	gravity.y = -10.0f;
	
	world = new b2World( worldAABB, gravity, false );
}

- (void)teardownPhysicsWorld
{
	
}

- (AAWActor*)addBoxAt:(GLfloat)x y:(GLfloat)y width:(GLfloat)width height:(GLfloat)height density:(GLfloat)density friction:(GLfloat)friction
{
	AAWActor* actor = nil;
	
	if( self.canHireActors )
	{
		b2BodyDef bodyDef;
		
		bodyDef.position.x = x;
		bodyDef.position.y = y;
		
		actor = [[[AAWActor alloc] initWithGame:self andDefinition:&bodyDef] autorelease];
		
		b2PolygonDef polyDef;
		polyDef.SetAsBox(width/2.0f, height/2.0f);
		polyDef.density = density;
		polyDef.friction = friction;	
		
		actor.body->CreateShape(&polyDef);
		
		if( density != 0 )
		{
			actor.body->SetMassFromShapes();
		}
		else
		{
			actor.body->PutToSleep();
		}
		
		[self hireActor:actor];
	}
		
	return actor;
}

- (void)addBoxGroupAt:(float)x y:(float)y
{
	AAWActor* a = nil;
	AAWActor* centre = nil;

	for(int i = 0; i < 5; i++)
	{
		a = [self addBoxAt: SCALE_PX_TO_WORLD((60 * drand48()) - (60/2) + x)
						 y: SCALE_PX_TO_WORLD((60 * drand48()) - (60/2) + y) 
					 width: SCALE_PX_TO_WORLD(5 + (15 * drand48())) 
					height: SCALE_PX_TO_WORLD(5 + (15 * drand48()))
				   density: 1.0f 
				  friction: 0.3f];
		if( a != nil )
		{
			a.red = 255 * drand48();
			
			b2Vec2 impulse( 0.0f, 0.5 * drand48() );
			b2Vec2 target( 0.0f, 0.0f );
			a.body->ApplyImpulse( impulse, target );
			
			if( centre == nil )
			{
				centre = a;
			}
			else
			{
				b2DistanceJointDef jointDef;
				jointDef.Initialize(centre.body, a.body, centre.body->GetWorldCenter(), a.body->GetWorldCenter());
				jointDef.length = SCALE_PX_TO_WORLD(25 + (40 * drand48()));
				jointDef.collideConnected = false;
				world->CreateJoint(&jointDef);
			}
		}
	}	
}

- (void)addEdges
{
	AAWActor* a;
	// Floor
	a = [self addBoxAt: 0.0f 
					 y: -(kScreenHeightWorldf/2.0f) + SCALE_PX_TO_WORLD(5) 
				 width: kScreenWidthWorldf 
				height: SCALE_PX_TO_WORLD(10)
			   density: 0.0f 
			  friction: 1.0f];
	if( a != nil ) a.green = 255;

	// Ceiling
	a = [self addBoxAt: 0.0f 
					 y: (kScreenHeightWorldf/2.0f) - SCALE_PX_TO_WORLD(5) 
				 width: kScreenWidthWorldf 
				height: SCALE_PX_TO_WORLD(10) 
			   density: 0.0f 
			  friction: 1.0f];
	if( a != nil ) a.green = 255;
	
	// Left
	a = [self addBoxAt: -(kScreenWidthWorldf/2.0f) + SCALE_PX_TO_WORLD(5)
					 y: 0.0f 
				 width: SCALE_PX_TO_WORLD(10) 
				height: kScreenHeightWorldf 
			   density: 0.0f 
			  friction: 1.0f];
	if( a != nil ) a.green = 255;
	
	// Right
	a = [self addBoxAt: (kScreenWidthWorldf/2.0f) - SCALE_PX_TO_WORLD(5)
					 y: 0.0f 
				 width: SCALE_PX_TO_WORLD(10) 
				height: kScreenHeightWorldf 
			   density: 0.0f 
			  friction: 1.0f];
	if( a != nil ) a.green = 255;
}

- (void)initGameWorld
{
	self.liveActors = [NSMutableArray arrayWithCapacity:100];
	self.deadActors	= [NSMutableArray arrayWithCapacity:100];
	
	[self reset];
}

- (void)teardownGameWorld
{
	self.deadActors = nil;
	self.liveActors = nil;
}



- (void)hireActor:(AAWActor*)actor
{
	if( self.canHireActors )
	{
		[liveActors addObject:actor];		
	}
	else
	{
		NSLog(@"Can't hire actor, no proxies left.");		
	}
	NSLog(@"Hired %d of %d", [liveActors count], b2_maxProxies);		
}

- (void)fireActor:(AAWActor*)actor immediately:(BOOL)immediately
{
	if( !immediately )
	{
		[deadActors addObject:actor];
	}
	
	[liveActors removeObjectIdenticalTo:actor];
}

- (void)clearOutTheDead
{
	[deadActors removeAllObjects];
}

- (void)reset
{
	[liveActors removeAllObjects];
	[self addEdges];
	[self addBoxGroupAt:0 y:0];
}

- (BOOL)isSceneDirty
{
	// As a default, assume any update tick makes something in the world dirty.
	return YES;
}

- (void)updatePhysics
{
	world->Step(self.updateInterval, 5);
}

- (void)updateLogic
{
	
}

- (void)render
{
	b2Body* curBody = world->GetBodyList();

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrthof( -kScreenWidthWorldf/2.0f, kScreenWidthWorldf/2.0f, -kScreenHeightWorldf/2.0f, kScreenHeightWorldf/2.0f, -10.0f, 10.0f);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	
	while (curBody != NULL) 
	{
		[(AAWActor*)curBody->GetUserData() render];
		curBody = curBody->GetNext();
	}
}

/**
 * Update the game. The looping here, which is
 * trying to keep the physics update independent
 * from the graphics update, is cheerfully
 * ripped from the following excellent articles:
 *
 *   http://gafferongames.com/game-physics/fix-your-timestep/
 *   http://www.nuclex.org/articles/xna-game-loop-basics
 */
- (void)tick
{
	BOOL updateGraphics = NO;
	NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
	NSTimeInterval delta = now - lastUpdate;
	lastUpdate = now;
	
	if( delta > self.maxCatchupInterval )
	{
		delta = self.maxCatchupInterval;
	}
	
	updateAccumulator += delta;
	
	while (updateAccumulator >= self.updateInterval)
	{
		[self updatePhysics];
		[self updateLogic];
		
		updateAccumulator -= self.updateInterval;
		
		// See if anything in the scene is dirty now.
		updateGraphics = updateGraphics || [self isSceneDirty];
		
		// Remove any dead actors
		[self clearOutTheDead];
	}
	
	if( updateGraphics )
	{
		[view drawStart];
		[self render];
		[view drawEnd];
	}
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	CGPoint location = [touch locationInView:view];

	[self addBoxGroupAt: location.x - 160 y: -(location.y - 240) ];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
	
}

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
	
}

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
	if( motion == UIEventSubtypeMotionShake )
	{
		[self reset];
	}
}

- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
	
}

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
{
	gravity.x = acceleration.x * 10.0f;
	gravity.y = acceleration.y * 10.0f;

	world->SetGravity(gravity);
}

@end
